/*
 * Copyright 2011 yura.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.opu.db_vdumper.util.datacopy;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opu.db_vdumper.ui.component.tabel.models.ColumnModel;
import org.opu.db_vdumper.ui.component.tabel.models.TableModel;
import org.opu.db_vdumper.util.Logger;
import org.opu.db_vdumper.util.monitors.DoubleProgresMonitor;
import org.opu.db_vdumper.util.monitors.SimpleProgresMonitor;

/**
 *   Generic data mover class. This class is designed to move
 * data from one database to another. To do this, first the
 * tables are created in the target database, then all data
 * from the source database is copied.
 * @author yura
 */
public class DataMover {
    public static final String ROWCOUNT = "SELECT COUNT(*) AS rowcount FROM ";

    /**
     * The source database.
     */
    private Database source;
    /**
     * The target database.
     */
    private Database target;
    /**
     * The list of tables, from the source database that needed to copy.
     */
    private List<TableModel> selectedModels;
    private List<TableModel> copiedTables = new ArrayList<TableModel>();

    public Database getSource() {
        return source;
    }

    public void setSource(Database source,  List<TableModel> selectedModels) {
        this.source = source;
        this.selectedModels = selectedModels;
    }

    public Database getTarget() {
        return target;
    }

    public void setTarget(Database target) {
        this.target = target;
    }

    /**
     * Create the specified table. To do this the source database will
     * be scanned for the table's structure. Then the table will be
     * created in the target database.
     *
     * @param table The name of the table to create.
     * @throws DatabaseException If a database error occurs.
     */
    public DbResponse createTable(String table, String schema) throws DatabaseException {
        String sql;
        DbResponse response = new DbResponse(true, null);

        // if the table already exists, then drop it
        if (target.tableExists(table, schema)) {
            sql = source.generateDrop(table, schema);
            response.add(target.execute(sql));
        }

        // now create the table
        sql = source.generateCreate(table, schema);
        response.add(target.execute(sql));

        return response;
    }

    /**
     * Create all of the tables in the database. This is done
     * by looping over the list of tables and calling createTable
     * for each.
     *
     * @throws DatabaseException If an error occurs.
     */
    public Map<String, DbResponse> createTables(SimpleProgresMonitor monitor)
            throws DatabaseException {

        Map<String, DbResponse> ret = new HashMap<String, DbResponse>();
        copiedTables = new ArrayList<TableModel>();
        Logger.getInstance().debug(this, "Create tables.");
        monitor.setNoteAndProgress("Create tables.", 1);

        int i = 1;
        int sz  = selectedModels.size();
        int max = SimpleProgresMonitor.MAX;
        
        for (TableModel tb : selectedModels) {
            DbResponse repns = createTable(tb.getName(), tb.getSchema());
            String name = tb.getSchema() + "." + tb.getName();

            ret.put(name, repns);
            copiedTables.add(tb);

            Logger.getInstance().debug(this, "Create table: " + tb.getBean());
            monitor.setNoteAndProgress(name, max / sz * i++);

            if (monitor.isCanceled()){
                break;
            }
        }

//        if (target instanceof FileDatabase){
//            target.close();
//        }

        return ret;
    }

    /**
     * Copy the data from one table to another. To do this
     * both a SELECT and INSERT statement must be created.
     * @param table The table to copy.
     * @throws DatabaseException If a database error occurs.
     */
    private void copyTable(TableModel table, DoubleProgresMonitor monitor)
            throws DatabaseException {

        StringBuilder selectSQL = new StringBuilder();
        StringBuilder insertSQL = new StringBuilder();
        StringBuffer values = new StringBuffer();

        Logger.getInstance().debug(this, "Begin copy: " + table.getBean());

//        Collection<String> columns = source.listColumns(table);
        Collection<String> columns = new ArrayList<String>();
        for (ColumnModel cm : table.getColumns()) {
            if (cm.isSelected()){
                Logger.getInstance().debug(this, "\t+Column: " + cm.getColumnBean());
                columns.add(cm.getColumnBean().getName());
            } else {
                Logger.getInstance().debug(this, "\t-Column: " + cm.getColumnBean());
            }
        }


        selectSQL.append("SELECT ");
        insertSQL.append("INSERT INTO ");
        String tableStr = DataMoverUtility.tableStr(table.getName(), table.getSchema());
        insertSQL.append(tableStr);
        insertSQL.append(" (");

        boolean first = true;
        for (String column : columns) {
            if (!first) {
                selectSQL.append(", ");
                insertSQL.append(", ");
                values.append(", ");
            } else {
                first = false;
            }

            selectSQL.append(DataMoverUtility.fieldNameStr(column));
            insertSQL.append(DataMoverUtility.fieldNameStr(column));
            values.append("?");
        }
        selectSQL.append(" FROM ");
        selectSQL.append(tableStr);

        insertSQL.append(") VALUES (");
        insertSQL.append(values);
        insertSQL.append(")");

        Logger.getInstance().debug(this, "\tselect SQL: " + selectSQL);
        Logger.getInstance().debug(this, "\tinsert SQL: " + insertSQL);

        // now copy
        MiniPreparedStatement statement = null;
        ResultSet rs = null;
        int sz = 1;

        try {

            monitor.setLowBarIndeterminate(false);
            try {
                ResultSet r = source.executeQuery(ROWCOUNT + tableStr);
                r.next();
                sz = r.getInt("rowcount") ;
                r.close() ;
            } catch (SQLException ex) {
                Logger.getInstance().info("\tCan't determine size table " + table);
                Logger.getInstance().debug(ex.getMessage());
                monitor.setLowBarIndeterminate(true);
            }

            statement = target.prepareStatement(insertSQL.toString());
            rs = source.executeQuery(selectSQL.toString());

            int rows = 0;

            while (rs.next()) {
                rows++;
                for (int i = 1; i <= columns.size(); i++) {
                    try {
						Object object = rs.getObject(i);
						statement.setObject(i, object);
					} catch (SQLException ex) {
						String str = rs.getString(i);
						statement.setString(i, str);
					}
                }
                statement.execute();

                monitor.setLowBarProgress(DoubleProgresMonitor.MAX / sz * rows);
                if (monitor.isCanceled()){
                    break;
                }
            }

            Logger.getInstance().debug(this, "\tCopied " + rows + " rows.");
        } catch (SQLException e) {
            throw (new DatabaseException(table.getSchema() + "." + table.getName(), e));
        } finally {
            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (SQLException e) {
                throw (new DatabaseException(e));
            }
            try {
                if (rs != null) {
                    statement.close();
                }
            } catch (SQLException e) {
                throw (new DatabaseException(e));
            }
        }
    }

    public void copyTableData(DoubleProgresMonitor monitor)
            throws DatabaseException {
        
        Logger.getInstance().info(this, "Copy data: " + copiedTables.size());
        
        int i = 1;
        int sz  = selectedModels.size();
        int max = SimpleProgresMonitor.MAX;
        
        for (TableModel table : copiedTables) {
            copyTable(table, monitor);

            String name = table.getSchema() + "." + table.getName();
            monitor.setNoteAndProgress(name, max / sz * i++);

            if (monitor.isCanceled()){
                break;
            }
        }
    }
}
